package org.apache.flume.node;

import java.io.File;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.apache.flume.CounterGroup;
import org.apache.flume.lifecycle.LifecycleAware;
import org.apache.flume.lifecycle.LifecycleState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Preconditions;
import com.google.common.eventbus.EventBus;
import com.google.common.util.concurrent.ThreadFactoryBuilder;

/**
 * PollingPropertiesFileConfigurationProvider --> PropertiesFileConfigurationProvider
 * PropertiesFileConfigurationProvider --> AbstractConfigurationProvider
 */
public class PollingPropertiesFileConfigurationProvider extends PropertiesFileConfigurationProvider implements LifecycleAware {

  private static final Logger LOGGER = LoggerFactory.getLogger(PollingPropertiesFileConfigurationProvider.class);

  private final EventBus eventBus;
  private final File file;
  private final int interval;
  private final CounterGroup counterGroup;
  private LifecycleState lifecycleState;

  private ScheduledExecutorService executorService;

  public PollingPropertiesFileConfigurationProvider(String agentName, File file, EventBus eventBus, int interval) {
    super(agentName, file); // agentName "flume-test"
    this.eventBus = eventBus;
    this.file = file; // file: "/todo/flume/example.conf"
    this.interval = interval; // interval: 30
    counterGroup = new CounterGroup(); // counterGroup: "{ name:null counter:{}}"
    lifecycleState = LifecycleState.IDLE;
  }

  @Override
  public void start() {
    LOGGER.info("Configuration provider starting");
    Preconditions.checkState(file != null, "The parameter file must not be null");
    // 创建线程池
    executorService = Executors.newSingleThreadScheduledExecutor( new ThreadFactoryBuilder().setNameFormat("conf-file-poller-%d").build());
    // 核心方法：创建执行体， 调用FileWatcherRunnable的run()方法
    FileWatcherRunnable fileWatcherRunnable = new FileWatcherRunnable(file, counterGroup); //
    // 开始进行调度
    executorService.scheduleWithFixedDelay(fileWatcherRunnable, 0, interval, TimeUnit.SECONDS);
    lifecycleState = LifecycleState.START;
    LOGGER.debug("Configuration provider started");
  }

  @Override
  public void stop() {
    LOGGER.info("Configuration provider stopping");

    executorService.shutdown();
    try {
      if (!executorService.awaitTermination(500, TimeUnit.MILLISECONDS)) {
        LOGGER.debug("File watcher has not terminated. Forcing shutdown of executor.");
        executorService.shutdownNow();
        while (!executorService.awaitTermination(500, TimeUnit.MILLISECONDS)) {
          LOGGER.debug("Waiting for file watcher to terminate");
        }
      }
    } catch (InterruptedException e) {
      LOGGER.debug("Interrupted while waiting for file watcher to terminate");
      Thread.currentThread().interrupt();
    }
    lifecycleState = LifecycleState.STOP;
    LOGGER.debug("Configuration provider stopped");
  }

  @Override
  public synchronized  LifecycleState getLifecycleState() {
    return lifecycleState;
  }


  @Override
  public String toString() {
    return "{ file:" + file + " counterGroup:" + counterGroup + "  provider:"
        + getClass().getCanonicalName() + " agentName:" + getAgentName() + " }";
  }

  public class FileWatcherRunnable implements Runnable {

    private final File file;
    private final CounterGroup counterGroup;

    private long lastChange;

    public FileWatcherRunnable(File file, CounterGroup counterGroup) {
      super();
      this.file = file;
      this.counterGroup = counterGroup;
      this.lastChange = 0L;
    }

    @Override
    public void run() {
      LOGGER.debug("Checking file:{} for changes", file);
      counterGroup.incrementAndGet("file.checks");
      long lastModified = file.lastModified();

      if (lastModified > lastChange) {
        LOGGER.info("Reloading configuration file:{}", file);
        counterGroup.incrementAndGet("file.loads");
        lastChange = lastModified;

        try {
          // 核心方法， getConfiguration()
          eventBus.post(getConfiguration()); //
        } catch (Exception e) {
          LOGGER.error("Failed to load configuration data. Exception follows.", e);
        } catch (NoClassDefFoundError e) {
          LOGGER.error("Failed to start agent because dependencies were not found in classpath. Error follows.", e);
        } catch (Throwable t) {
          LOGGER.error("Unhandled error", t);
        }
      }
    }
  }

}
